#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"

// 可以搞一个配置文件 服务端启动时读文件设置web根目录
const std::string webRoot = "./WebRoot"; // web 根目录
// const std::string homePage = "WebRscs/Home/home.html";
const std::string homePage = "WebRscs/Home/shopWeb/index.html";
// const std::string homePage = "WebRscs/Home/test.html";
const std::string SEP = "\r\n";

class HttpServer;
class ThreadInfo
{
public:
    ThreadInfo(int socketFd, HttpServer *svr)
        : _socketFd(socketFd),
          _svr(svr)
    {
    }

public:
    int _socketFd;
    HttpServer *_svr;
};

// 服务端
class HttpRequest
{
public:
    // 分析客户端发来的请求
    // 把请求行和请求头放在reqHeader
    // 把请求正文放在mainPage
    void Analyse(std::string req)
    {
        while (true)
        {
            // req不存在SEP 说明只剩下正文了
            std::size_t pos = req.find(SEP);
            if (pos == std::string::npos)
                break;

            // 每一个SEP前都是一行 尾插保存
            std::string perLine = req.substr(0, pos);
            // 遇到空行 请求行和请求头结束
            if (perLine.empty())
                break;

            _reqHeader.push_back(perLine);
            req.erase(0, pos + SEP.size());
        }
        // 剩下的都是请求正文
        _mainPage = req;
    }

    // 把http请求的相关信息分割 存入相应的变量
    void Separate()
    {
        // 把请求行中的三部分别存进对应的变量
        std::stringstream ss(_reqHeader[0]);
        ss >> _method >> _url >> _httpVersion;

        // 获取url要访问的资源文件
        // 如果客户端想访问"/"或者"/home.html" 统一返回主页面
        _urlSrcPath = webRoot; // webRoot = "./WebRoot";
        if (_url == "/" || _url == "/home.html")
        {
            _urlSrcPath += "/";
            _urlSrcPath += homePage; // homePage = "WebRscs/Home/home.html"
        }
        else
            _urlSrcPath += _url; // /a/b/c/d.html->./WebRoot/a/b/c/d.html

        // 获取url要访问的资源文件的后缀
        auto pos = _urlSrcPath.rfind(".");
        if (pos == std::string::npos)
            _urlSrcSuffix = ".html";
        else
            _urlSrcSuffix = _urlSrcPath.substr(pos);
    }

    // 查看解析客户端发的http请求是否成功
    void DebugPrint()
    {
        std::cout << "------------------------------分割信息是否成功begin--------------------------------" << std::endl;

        for (auto &line : _reqHeader)
            std::cout << line << "\n\n";
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "http_version: " << _httpVersion << std::endl;
        std::cout << "urlSrcPath: " << _urlSrcPath << std::endl;
        std::cout << "urlSrcSuffix: " << _urlSrcSuffix << std::endl;
        std::cout << "mainPage: " << _mainPage << std::endl;

        std::cout << "------------------------------分割信息是否成功end--------------------------------" << std::endl;
    }

public:
    std::vector<std::string> _reqHeader; // 请求行+请求头
    std::string _mainPage;               // 请求正文

    // 把请求行中的三部分别存进对应的变量
    std::string _method;
    std::string _url;
    std::string _httpVersion;
    std::string _urlSrcPath;
    std::string _urlSrcSuffix;
};

static const int g_defaultPort = 8080;
class HttpServer
{
public:
    HttpServer(uint16_t port = g_defaultPort)
        : _port(port)
    {
        _contentType.insert({".html", "text/html"});
        _contentType.insert({".png", "images/png"});
        _contentType.insert({".jpg", "images/jpg"});
        _contentType.insert({".mp4", "videos/mp4"});
    }
    // 读取htmlPath路径下的html存入string
    static std::string ReadSpecifiedRsc(const std::string &htmlPath)
    {
        std::ifstream in(htmlPath, std::ios::binary);
        // 客户端想要访问的资源不存在 返回空串
        if (!in.is_open())
            return "";

        // 1.方法一：getline
        // std::string content;
        // std::string line;
        // while (std::getline(in, line))
        //     content += line;

        // 2.方法二：ifstream::read()
        // 计算html页面数据长度（字节长度）
        in.seekg(0, std::ios_base::end); // 定位读取指针到文件尾
        auto len = in.tellg();           // 计算从文件头到现在的读取指针的长度
        in.seekg(0, std::ios_base::beg); // 重新定位读取指针到文件头 目的是不影响下次读取

        std::string content;
        content.resize(len);
        in.read((char *)content.c_str(), content.size());

        // 3.方法三: istreambuf_iterator
        //  std::string content(
        //      std::istreambuf_iterator<char>(in),
        //      (std::istreambuf_iterator<char>()) // 默认构造的istreambuf_iterator表示EOF
        //  );

        in.close();
        return content;
    }
    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter = _contentType.find(suffix);
        if (iter == _contentType.end())
            return _contentType[".html"];
        else
            return _contentType[suffix];
    }
    void HandlerHttp(int sockfd)
    {
        char readedReq[10240];
        ssize_t recvBytes = recv(sockfd, readedReq, sizeof(readedReq) - 1, 0);
        if (recvBytes > 0)
        {
            std::cout << std::endl
                      << "@#￥%&@#￥%&@#￥%&@#￥%&本次测试开始了@#￥%&@#￥%&@#￥%&@#￥%&" << std::endl
                      << std::endl;
            // 把读到的信息输出到stdin
            // 通常是浏览器发来的http请求
            // 假设读取到的就是一个完整独立的http请求
            // 涉及到读取 实际上都应该做读写控制 因为无法保证读取到的是完整的数据
            // 这里我们实现的是简易版httpServer 并没有实现此项功能
            std::cout << std::endl
                      << "=============================查看获取到的请求begin=====================" << std::endl
                      << std::endl;
            readedReq[recvBytes] = 0;
            std::cout << readedReq << std::endl;
            std::cout << std::endl
                      << "=============================查看获取到的请求end=====================" << std::endl
                      << std::endl;

            HttpRequest req;
            req.Analyse(readedReq); // 请求行/头放在reqHeader 请求正文放在mainPage
            req.Separate();         // http请求的相关信息分割 存入相应的变量
            // req.DebugPrint();//查看分割信息是否成功

            // 服务端收到客户端的http请求
            // 构建一个http响应发给客户端
            // 0.响应正文
            std::string mainText;
            bool mainTextOk = true;
            // 客户端访问的可能是html txt img video
            mainText = ReadSpecifiedRsc(req._urlSrcPath);
            // 404的处理：客户端想要访问的资源不存在
            if (mainText.empty())
            {
                mainTextOk = false;
                std::string errHtml = webRoot; //./WebRoot
                errHtml += "/";
                errHtml += "WebRscs/Home/err.html";
                mainText = ReadSpecifiedRsc(errHtml);
            }

            // 1.响应行
            std::string responseLine;
            if (mainTextOk == true)
                responseLine = "HTTP/1.0 200 OK\r\n";
            else
                responseLine = "HTTP/1.0 404 Not Found\r\n";
            // 临时重定向: 如果服务器构建的响应中包含该字段和新网址w 浏览器接收后识别到
            // 将会做出以下行为：二次向服务端发起请求 访问w
            // responseLine = "HTTP/1.0 302 Found\r\n";

            // 2.响应头
            std::string responseHeader = "Content-Length: ";
            responseHeader += std::to_string(mainText.size());
            responseHeader += SEP;
            responseHeader += "Content-Type: ";
            responseHeader += SuffixToDesc(req._urlSrcSuffix);
            responseHeader += SEP;
            // 此处仅为测试 实际上的该字段内容从浏览器输入的信息提取
            // 浏览器收到响应后 对该字段会：截取信息 保存到cookie文件
            // 并在下次发起请求时携带该属性
            responseHeader += "Set-Cookie: name=lhr";
            responseHeader += SEP;
            responseHeader += "Set-Cookie: passwd=666";
            responseHeader += SEP;
            responseHeader += "Set-Cookie: view=test.html";
            responseHeader += SEP;
            // 临时重定向
            // responseHeader += "Location: https://www.qq.com\r\n";

            // 3.空行
            std::string blankLine = SEP;

            // 完整响应
            std::string response = responseLine;
            response += responseHeader;
            response += blankLine;
            response += mainText;

            // 查看构建的响应的信息
            std::cout << std::endl
                      << "$$$$$$$$$$$$$$$$$$$$$$$$$查看构建的响应begin$$$$$$$$$$$$$$$$$$$$$$$$$" << std::endl
                      << std::endl;

            if (req._urlSrcSuffix == ".png" || req._urlSrcSuffix == ".jpg")
            {
                std::cout << responseLine;
                std::cout << responseHeader;
                std::cout << blankLine;
                std::cout << "this is a img!" << std::endl;
            }
            else if (req._urlSrcSuffix == ".mp4")
            {
                std::cout << responseLine;
                std::cout << responseHeader;
                std::cout << blankLine;
                std::cout << "this is a video!" << std::endl;
            }
            else
                std::cout << response << std::endl;

            std::cout << std::endl
                      << "$$$$$$$$$$$$$$$$$$$$$$$$$查看构建的响应end$$$$$$$$$$$$$$$$$$$$$$$$$$$$" << std::endl
                      << std::endl;

            log(Info, "show last access time\n");
            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());

        ThreadInfo *threadinfo = static_cast<ThreadInfo *>(args);
        // ThreadRun是一个静态函数 静态函数不能调用非静态函数
        // 则HandlerHttp要想被调用只能称为静态 如此 HandlerHttp中调用的函数也必须是静态函数
        // 通过在args中多加一个参数可以使得HandlerHttp不是静态函数
        threadinfo->_svr->HandlerHttp(threadinfo->_socketFd);

        delete threadinfo;
        return nullptr;
    }
    bool Start()
    {
        _listenSock.Socket();
        _listenSock.Bind(_port);
        _listenSock.Listen();
        while (true)
        {
            std::string clientIp;
            uint16_t clientPort;
            int sockfd = _listenSock.Accept(&clientIp, &clientPort);
            if (sockfd < 0)
                continue;
            log(Info, "accept a new connect, sockfd: %d", sockfd);

            pthread_t tid;
            ThreadInfo *threadinfo = new ThreadInfo(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, threadinfo);
        }
    }

    ~HttpServer()
    {
    }

private:
    Sock _listenSock;
    uint16_t _port;
    std::unordered_map<std::string, std::string> _contentType;
};
